/*
	see copyright notice in sqmod.h
*/
#include "sqmod.h"
#include <stdio.h>

// simple simple wrapping of dlfcn functions for win32, not even sure it follows all the rules
// but it does cover the very basics of the functions. It also ignores the possible flags for dlopen()
// as they have no equivelant on a WIN32 system.
#ifndef HAVE_DLFCN
    #ifdef WIN32
        #define RTLD_LAZY 0
        #define RTLD_NOW 0
        #define RTLD_GLOBAL 0
        #define RTLD_LOCAL 0
        #include <windows.h>
        void  *dlopen(const char *fn, int i) { return (void*)LoadLibrary(fn); }
        void  *dlsym(void *m, const char *sym) { return (void*)GetProcAddress((HMODULE)m,sym); }
        int    dlclose(void *m) { return FreeLibrary((HMODULE)m); }
        char  *dlerror(void) { return NULL; }
    #else
        #error No dlfcn.h, but not WIN32, not able to deal!
    #endif
#else
    #include <dlfcn.h>
#endif

#ifdef SQUNICODE
    #error Unicode support for module code is currently broken.
#endif

SQInteger file_lexfeedASCII(SQUserPointer file)
{
    int ret;
    char c;
    if ( ( ret=fread(&c,sizeof(c),1,(FILE *)file )>0) ) return c;
    return 0;
}

SQRESULT compile_file(HSQUIRRELVM v,const char *filename)
{
    FILE *f=fopen(filename,"rb");
    if (f)
    {
         sq_compile(v,file_lexfeedASCII,f,filename,1);
         fclose(f);
         return SQTrue;
    }
    return SQFalse;
}

HSQUIRRELFRAME sq_newframe(HSQUIRRELVM vm)
{
    HSQUIRRELFRAME frame = (HSQUIRRELFRAME)sq_malloc(sizeof(HSQUIRRELFRAME));

    // establish the basics for this frame
    frame->version = SQUIRREL_VERSION;
    frame->vm = vm;

    // add all of the API to the frame's pointers
    frame->open = sq_open;
    frame->newthread = sq_newthread;
    frame->seterrorhandler = sq_seterrorhandler;
    frame->close = sq_close;
    frame->setforeignptr = sq_setforeignptr;
    frame->getforeignptr = sq_getforeignptr;
    frame->setprintfunc = sq_setprintfunc;
    frame->getprintfunc = sq_getprintfunc;
    frame->suspendvm = sq_suspendvm;
    frame->wakeupvm = sq_wakeupvm;
    frame->getvmstate = sq_getvmstate;

    frame->compile = sq_compile;
    frame->compilebuffer = sq_compilebuffer;
    frame->enabledebuginfo = sq_enabledebuginfo;
    frame->notifyallexceptions = sq_notifyallexceptions;
    frame->setcompilererrorhandler = sq_setcompilererrorhandler;

    frame->push = sq_push;
    frame->pop = sq_pop;
    frame->poptop = sq_poptop;
    frame->remove = sq_remove;
    frame->gettop = sq_gettop;
    frame->settop = sq_settop;
    frame->reservestack = sq_reservestack;
    frame->cmp = sq_cmp;
    frame->move = sq_move;

    frame->newuserdata = sq_newuserdata;
    frame->newtable = sq_newtable;
    frame->newarray = sq_newarray;
    frame->newclosure = sq_newclosure;
    frame->setparamscheck = sq_setparamscheck;
    frame->bindenv = sq_bindenv;
    frame->pushstring = sq_pushstring;
    frame->pushfloat = sq_pushfloat;
    frame->pushinteger = sq_pushinteger;
    frame->pushbool = sq_pushbool;
    frame->pushuserpointer = sq_pushuserpointer;
    frame->pushnull = sq_pushnull;
    frame->gettype = sq_gettype;
    frame->getsize = sq_getsize;
    frame->getbase = sq_getbase;
    frame->instanceof = sq_instanceof;
    frame->tostring = sq_tostring;
    frame->tobool = sq_tobool;
    frame->getstring = sq_getstring;
    frame->getinteger = sq_getinteger;
    frame->getthread = sq_getthread;
    frame->getbool = sq_getbool;
    frame->getuserpointer = sq_getuserpointer;
    frame->getuserdata = sq_getuserdata;
    frame->settypetag = sq_settypetag;
    frame->gettypetag = sq_gettypetag;
    frame->setreleasehook = sq_setreleasehook;
    frame->getscratchpad = sq_getscratchpad;
    frame->getclosureinfo = sq_getclosureinfo;
    frame->setnativeclosurename = sq_setnativeclosurename;
    frame->setinstanceup = sq_setinstanceup;
    frame->getinstanceup = sq_getinstanceup;
    frame->setclassudsize = sq_setclassudsize;
    frame->newclass = sq_newclass;
    frame->createinstance = sq_createinstance;
    frame->setattributes = sq_setattributes;
    frame->getattributes = sq_getattributes;
    frame->getclass = sq_getclass;
    frame->weakref = sq_weakref;
    frame->getdefaultdelegate = sq_getdefaultdelegate;

    frame->pushroottable = sq_pushroottable;
    frame->pushregistrytable = sq_pushregistrytable;
    frame->pushconsttable = sq_pushconsttable;
    frame->setroottable = sq_setroottable;
    frame->setconsttable = sq_setconsttable;
    frame->newslot = sq_newslot;
    frame->deleteslot = sq_deleteslot;
    frame->set = sq_set;
    frame->get = sq_get;
    frame->rawset = sq_rawset;
    frame->rawget = sq_rawget;
    frame->rawdeleteslot = sq_rawdeleteslot;
    frame->arrayappend = sq_arrayappend;
    frame->arraypop = sq_arraypop;
    frame->arrayresize = sq_arrayresize;
    frame->arrayreverse = sq_arrayreverse;
    frame->arrayremove = sq_arrayremove;
    frame->arrayinsert = sq_arrayinsert;
    frame->setdelegate = sq_setdelegate;
    frame->getdelegate = sq_getdelegate;
    frame->clone = sq_clone;
    frame->setfreevariable = sq_setfreevariable;
    frame->next = sq_next;
    frame->getweakrefval = sq_getweakrefval;
    frame->clear = sq_clear;

    frame->call = sq_call;
    frame->resume = sq_resume;
    frame->getlocal = sq_getlocal;
    frame->getfreevariable = sq_getfreevariable;
    frame->throwerror = sq_throwerror;
    frame->reseterror = sq_reseterror;
    frame->getlasterror = sq_getlasterror;

    frame->getstackobj = sq_getstackobj;
    frame->pushobject = sq_pushobject;
    frame->addref = sq_addref;
    frame->release = sq_release;
    frame->resetobject = sq_resetobject;
    frame->objtostring = sq_objtostring;
    frame->objtobool = sq_objtobool;
    frame->objtointeger = sq_objtointeger;
    frame->objtofloat = sq_objtofloat;
    frame->getobjtypetag = sq_getobjtypetag;

    frame->collectgarbage = sq_collectgarbage;

    frame->writeclosure = sq_writeclosure;
    frame->readclosure = sq_readclosure;

    frame->malloc = sq_malloc;
    frame->realloc = sq_realloc;
    frame->free = sq_free;

    frame->stackinfos = sq_stackinfos;
    frame->setdebughook = sq_setdebughook;

    return frame;
}

void sq_freeframe(HSQUIRRELFRAME hf)
{
    sq_free(hf,sizeof(HSQUIRRELFRAME));
}

HSQUIRRELFRAME sq_findframe(HSQUIRRELVM vm)
{
    HSQUIRRELFRAME hf;

    sq_pushregistrytable(vm);
    sq_pushstring(vm,"_frame_",-1);
    if (sq_rawget(vm,-2) == SQFalse)
    {
        sq_poptop(vm);
        return NULL;
    }
    sq_getuserpointer(vm,-1,(SQUserPointer*)&hf);
    sq_pop(vm,2);
    return hf;
}

SQRESULT sq_addmodulesupport(HSQUIRRELVM vm)
{
    HSQUIRRELFRAME hf;
    // first we need to add a special registry variable into the vm for the frame
    // also, check to see if it already exists!
    sq_pushregistrytable(vm);
    sq_pushstring(vm,"_frame_",-1);
    if (sq_rawget(vm,-2) == SQFalse)
    {
        // we need to create a new frame for this vm and add the needed functions
        hf = sq_newframe(vm);
        sq_pushstring(vm,"_frame_",-1);
        sq_pushuserpointer(vm,hf);
        sq_rawset(vm,-3);
        sq_poptop(vm);
        // ok, we have added the needed frame struct into the registry table
        // now register our functions
        sq_pushroottable(vm);
        sq_pushstring(vm,"import",-1);
        sq_newclosure(vm,sq_sqimportmodule,0);
        sq_newslot(vm,-3,SQFalse);
        sq_pushstring(vm,"importdll",-1);
        sq_newclosure(vm,sq_sqimportdll,0);
        sq_newslot(vm,-3,SQFalse);
        // now add a new table "_modules_" that will hold the module names and versions
        sq_pushstring(vm,"_modules_",-1);
        sq_newtable(vm);
        sq_newslot(vm,-3,SQFalse);
        // clean up and return
        sq_poptop(vm);
        return SQTrue;
    }
    return SQFalse;
}

SQInteger sq_sqimportmodule(HSQUIRRELVM vm)
{
    const SQChar *mname;
    SQChar *fname;
    int len;
    SQInteger ret;

    // ok we take the module name and look for a .nut first, then a .dll on WIN32 or a .so otherwise

    // ok we expect one parameter, a string with a module name
    if (sq_getstring(vm,1,&mname) == SQFalse) return SQ_ERROR;
    len = sq_getsize(vm,1);

    fname = sq_malloc((len+5)*sizeof(SQChar));
    sprintf(fname,"%s.nut",mname);

    if (compile_file(vm,fname) != SQTrue)
    {
        // no .nut, lets try for a binary now
        #ifdef WIN32
            sprintf(fname,"%s.dll",mname);
        #else
            sprintf(fname,"%s.so",mname);
        #endif
        sq_pushstring(vm,fname,-1);
        ret = sq_sqimportdll(vm);
        sq_poptop(vm);
        return ret;
    } else
    {
        // the .nut compiled, so call it!
        if (sq_call(vm,0,SQFalse,SQTrue) != SQTrue) return SQ_ERROR;
        return 0;
    }
}

SQRESULT sq_importdllf(HSQUIRRELFRAME hf, const SQChar *fname)
{
    void *mod;
    SQIMPORTFUNCTION sq_import;

    mod = dlopen(fname,RTLD_NOW);
    if (mod == NULL) return SQFalse;    // kick back out if the file is not a dll/so!

    sq_import = (SQIMPORTFUNCTION)dlsym(mod,"sq_import");
    if (sq_import == NULL) return SQFalse;  // kick back out if the dll/so does not have the sq_import() entry point

    return sq_import(hf);               // finally call the dll's/so's entry point and return its result
}

SQInteger sq_sqimportdll(HSQUIRRELVM vm)
{
    HSQUIRRELFRAME hf;
    const SQChar *fname;

    // first things first, we are going to need to see if we have module support!
    hf = sq_findframe(vm);
    if (!hf) return SQ_ERROR;

    // ok we expect one parameter, a string with a dll/so filename
    if (sq_getstring(vm,1,&fname) == SQFalse) return SQ_ERROR;

    // import the module and error out if it does not return the proper value
    if (sq_importdllf(hf,fname) != SQTrue) return SQ_ERROR;

    return 0;
}

SQRESULT sq_importdll(HSQUIRRELVM vm, const SQChar *fname)
{
    HSQUIRRELFRAME hf;
    void *mod;
    SQIMPORTFUNCTION sq_import;

    mod = dlopen(fname,RTLD_NOW);
    if (mod == NULL) return SQFalse;    // kick back out if the file is not a dll/so!

    hf = sq_findframe(vm);
    if (!hf) return SQFalse;            // kick back out if the vm has no module support!

    sq_import = (SQIMPORTFUNCTION)dlsym(mod,"sq_import");
    if (sq_import == NULL) return SQFalse;  // kick back out if the dll/so does not have the sq_import() entry point

    return sq_import(hf);               // finally call the dll's/so's entry point and return its result
}
